package main;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.HashMap;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings("serial")
public class MathBasicArithmetic 
{
	private static String[] funcoes = new String[] { "sin", "cos", "tan", "log", "ln", "abs" };
	
	private static HashMap<String, Double> constantes = new HashMap<String, Double>()
			{{
				put("pi", Math.PI);
				put("e", Math.E);
			}};
	
	//Padrões Regex
	private static Pattern parenteses = Pattern.compile("([a-z]*)\\(([^\\(\\)]+)\\)(\\^|!?)");
	private static Pattern pot = Pattern.compile("(-?\\d+\\.?\\d*)\\^(-?\\d+\\.?\\d*)");
	private static Pattern mult = Pattern.compile("(-?\\d+\\.?\\d*)\\*(-?\\d+\\.?\\d*)");
	private static Pattern div = Pattern.compile("(-?\\d+\\.?\\d*)/(-?\\d+\\.?\\d*)");
	private static Pattern soma = Pattern.compile("(-?\\d+\\.?\\d*)\\+(-?\\d+\\.?\\d*)");
	private static Pattern subtr = Pattern.compile("(-?\\d+\\.?\\d*)-(-?\\d+\\.?\\d*)");
    
    //Formato de numeros decimais
	static DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(Locale.UK);
    
	public static String eval(String exp)
	{
		df.setMaximumFractionDigits(15);
		
		
		try
		{
			//Retirar os espaços
			exp = exp.replaceAll("\\s+","");
			
			
			//Substituir as constantes
			for (String con : constantes.keySet()) {
				exp = exp.replaceAll("(?<![A-Za-z])" + con + "(?![A-Za-z])", df.format(constantes.get(con).doubleValue()));
			}
			
			Matcher mat;
			
			//Executar funcoes
			for(int i=0; i < funcoes.length; i++)
			{
				Pattern pat = Pattern.compile("(" + funcoes[i] + "\\((.*?)\\))");
				mat = pat.matcher(exp);
				
				Matcher mat2;
				
				while(mat.find())
				{
					exp = exp.replace(mat.group(2), parse(mat.group(2)));
					mat2 = pat.matcher(exp);
					
					while(mat2.find())
					{
						exp = exp.replace(mat2.group(1), Double.toString(execFunc(funcoes[i], mat2.group(2))));
						mat = pat.matcher(exp);
					}
				}
				
			}
			
			
			//Processar cada par de parenteses
			mat = parenteses.matcher(exp);
			
			while(mat.find())
			{
				exp = exp.replace("(" + mat.group(2) + ")", parse(mat.group(2)));
				mat = parenteses.matcher(exp);
			}
			
			return parse(exp);
			
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return "Erro de sintaxe!";
		}
	}
	
	public static double round15(double x){
	    DecimalFormat twoDForm = new DecimalFormat("0.##############E0");
	    String str = twoDForm.format(x);
	    return Double.valueOf(str);
	}
	
	private static String parse(String exp)
	{
		Matcher mat;

		
		try
		{
			//Fazer os calculos pela respectiva ordem
			//Expoentes
			while(exp.contains("^"))
			{
				mat = pot.matcher(exp);
				while(mat.find())
				{
					double val1 = Double.parseDouble(mat.group(1));
					double val2 = Double.parseDouble(mat.group(2));
					String val = "";
					
					if(val1 < 0)
					{
						val1 = Math.abs(val1);
						val = df.format(-Math.pow(val1, val2));
					}
					else
						val = df.format(Math.pow(val1, val2));
					
					exp = exp.replace(mat.group(0), val);
					mat = pot.matcher(exp);
				}
			}
			
			//Multiplicações
			while(exp.contains("*"))
			{
				mat = mult.matcher(exp);
				while(mat.find())
				{
					String val = df.format(Double.parseDouble(mat.group(1)) * Double.parseDouble(mat.group(2)));
					exp = exp.replace(mat.group(0), val);
					mat = pot.matcher(exp);
				}
			}
			
			//Divisões
			while(exp.contains("/"))
			{
				mat = div.matcher(exp);
				while(mat.find())
				{
					String val = df.format(Double.parseDouble(mat.group(1)) / Double.parseDouble(mat.group(2)));
					exp = exp.replace(mat.group(0), val);
					mat = pot.matcher(exp);
				}
			}
			
			//Adições
			while(exp.contains("+"))
			{
				mat = soma.matcher(exp);
				while(mat.find())
				{
					String val = df.format(Double.parseDouble(mat.group(1)) + Double.parseDouble(mat.group(2)));
					exp = exp.replace(mat.group(0), val);
					mat = pot.matcher(exp);
				}
			}
			
			//Subtracções
			while(exp.contains("-"))
			{
				mat = subtr.matcher(exp);
				String before = exp;
				
				while(mat.find())
				{
					String val = df.format(Double.parseDouble(mat.group(1)) - Double.parseDouble(mat.group(2)));
					exp = exp.replace(mat.group(0), val);
					mat = pot.matcher(exp);
				}
				
				//Numeros negativos impedem o ciclo de acabar portanto se não houver alterações após uma iteração, sair do ciclo
				if(exp == before)
					break;
			}
		}
		catch(Exception e)
		{
			System.out.println("Erro de sintaxe!");
		}
		return exp;
	}
	
	private static double execFunc(String func, String param)
	{
		switch(func)
		{
			case "abs": 
				return Math.abs(Double.parseDouble(param));
			case "sin":
				return round15(Math.sin(Double.parseDouble(param))-1)+1;
			case "cos":
				return round15(Math.cos(Double.parseDouble(param))-1)+1;
			case "tan":
				return round15(Math.tan(Double.parseDouble(param))-1)+1;
			case "ln":
				return Math.log(Double.parseDouble(param));
			case "log":
				return Math.log10(Double.parseDouble(param));
		}
		return 0;
	}

}

